package ru.evga.gwt.lib.common.client.actitvity;

import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import ru.evga.gwt.lib.common.client.domain.BaseEntityDto;
import ru.evga.gwt.lib.common.client.domain.ResultContainer;
import ru.evga.gwt.lib.common.client.exception.ExceptionHandler;
import ru.evga.gwt.lib.common.client.request.IServiceRequestCallback;
import ru.evga.gwt.lib.common.client.request.RequestFactory;
import ru.evga.gwt.lib.common.client.request.params.FindEntityParams;
import ru.evga.gwt.lib.common.client.request.params.FindEntityRangeParams;
import ru.evga.gwt.lib.common.client.request.params.RemoveEntityParams;
import ru.evga.gwt.lib.common.client.ui.dialog.ConfirmDialog;
import ru.evga.gwt.lib.common.client.ui.editor.ISaveEntityCallback;
import ru.evga.gwt.lib.common.client.view.IEntityListView;

import com.google.gwt.activity.shared.Activity;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.event.shared.EventBus;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.cellview.client.AbstractHasData;
import com.google.gwt.user.client.ui.AcceptsOneWidget;
import com.google.gwt.view.client.HasData;
import com.google.gwt.view.client.ProvidesKey;
import com.google.gwt.view.client.Range;
import com.google.gwt.view.client.RangeChangeEvent;
import com.google.gwt.view.client.SelectionChangeEvent;
import com.google.gwt.view.client.SingleSelectionModel;


/**
 * Abstract activity for displaying a list of {@link EntityProxy}. These
 * activities are not re-usable. Once they are stopped, they cannot be
 * restarted.
 * <p>
 * Subclasses must:
 * 
 * <ul>
 * <li>provide a {@link IProxyListView}
 * <li>implement method to request a full count
 * <li>implement method to find a range of entities
 * <li>respond to "show details" commands
 * </ul>
 * <p>
 * Only the properties required by the view will be requested.
 * 
 * @param <P>
 *            the type of {@link EntityProxy} listed
 */
public abstract class AbstractEntityListActivity<P extends BaseEntityDto>
    implements Activity, IEntityListView.Delegate<P>, ISaveEntityCallback<P>
{
    /**
     * This mapping allows us to update individual rows as records change.
     */
    private final Map<Integer, Integer> idToRow = new HashMap<Integer, Integer>();
    private final Map<Integer, P> idToDto = new HashMap<Integer, P>();
    private final SingleSelectionModel<P> selectionModel;
    private HandlerRegistration rangeChangeHandler;
    private IEntityListView<P> view;
    private AcceptsOneWidget display;

    public AbstractEntityListActivity(IEntityListView<P> view)
    {
        this.view = view;
        view.setDelegate(this);
        final HasData<P> hasData = view.asHasData();
        
        rangeChangeHandler = hasData.addRangeChangeHandler(new RangeChangeEvent.Handler()
        {
            public void onRangeChange(RangeChangeEvent event)
            {
                AbstractEntityListActivity.this.onRangeChanged(hasData);
            }
        });
        // Inherit the view's key provider
        ProvidesKey<P> keyProvider = ((AbstractHasData<P>)hasData).getKeyProvider();
        selectionModel = new SingleSelectionModel<P>(keyProvider);
        hasData.setSelectionModel(selectionModel);
        selectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler()
        {
            public void onSelectionChange(SelectionChangeEvent event)
            {
                P selectedObject = selectionModel.getSelectedObject();
                if (selectedObject != null)
                {
                    onSelected(selectedObject);
                }
            }
        });
    }

    public IEntityListView<P> getView()
    {
        return view;
    }

    public String mayStop()
    {
        return null;
    }

    public void onCancel()
    {
        onStop();
    }
    
    public void applyFilter()
    {
        init();   
    }

    /**
     * Called by the table as it needs data.
     */
    public void onRangeChanged(HasData<P> listView)
    {
        final Range range = listView.getVisibleRange();
        final IServiceRequestCallback<ResultContainer<P>, String> callback = new IServiceRequestCallback<ResultContainer<P>, String>()
        {
            public void ok(ResultContainer<P> data)
            {
                JsArray<P> values = data.getResult();
                if (view == null)
                {
                    // This activity is dead
                    return;
                }
                idToRow.clear();
                idToDto.clear();
                List<P> list = new LinkedList<P>();
                for (int i = 0, row = range.getStart(); i < values.length(); i++, row++)
                {
                    P entity = values.get(i);
                    Integer entityId = entity.getId();
                    idToRow.put(entityId, row);
                    idToDto.put(entityId, entity);
                    list.add(entity);
                }
                getView().asHasData().setRowData(range.getStart(), list);
                if (display != null)
                {
                    display.setWidget(getView());
                }
            }

            public void error(String data)
            {
                ExceptionHandler.handle(data);
            }
        };
        fireRangeRequest(range.getStart(), range.getLength(), callback);
    }

    public void onStop()
    {
        view.setDelegate(null);
        view = null;
        rangeChangeHandler.removeHandler();
        rangeChangeHandler = null;
    }

    /**
     * Select the given record, or clear the selection if called with null or an
     * id we don't know.
     */
    public void select(BaseEntityDto entityId)
    {
        /*
         * The selectionModel will not flash if we put it back to the same state
         * it is already in, so we can keep this code simple.
         */
        // Clear the selection
        P selected = selectionModel.getSelectedObject();
        if (selected != null)
        {
            selectionModel.setSelected(selected, false);
        }
        // Select the new proxy, if it's relevant
        if (entityId != null)
        {
            P selectMe = idToDto.get(entityId);
            selectionModel.setSelected(selectMe, true);
        }
    }

    public void start(AcceptsOneWidget display, EventBus eventBus)
    {
        view.setDelegate(this);
        this.display = display;
        init();
    }

    //    public void update(WriteOperation writeOperation, EntityProxyId<P> proxyId)
    //    {
    //        switch (writeOperation)
    //        {
    //        case UPDATE:
    //            update(proxyId);
    //            break;
    //
    //        case DELETE:
    //            init();
    //            break;
    //
    //        case PERSIST:
    //            /*
    //             * On create, we presume the new record is at the end of the list,
    //             * so fetch the last page of items.
    //             */
    //            getLastPage();
    //            break;
    //        }
    //    }
    
    public void onEntitySaved(P savedEntity) 
    {
        Integer savedEntityId = savedEntity.getId();
        if(idToDto.containsKey(savedEntityId))
        {
            update(savedEntityId);
        }
        else
        {
            init();
        }
        
    };

    public void onRemoveClicked(final P item) 
    {
        ConfirmDialog dialog = new ConfirmDialog("Remove?", ConfirmDialog.BUTTON_TYPE.YES_NO, new ConfirmDialog.AbstractConfirmDialogHandler()
        {
            
            public void onYes()
            {
                RemoveEntityParams params = RemoveEntityParams.createEmptyParams();
                params.setEntityName(getEntityName());
                params.setId(item.getId());
                RequestFactory.removeEntity(params, new IServiceRequestCallback<Void, String>()
                {
                    public void ok(Void data)
                    {
                        init();
                    }
                    
                    public void error(String data)
                    {
                        ExceptionHandler.handle(data);
                    }
                });
                

            }
        });
        dialog.setTitle("Remove");
        dialog.open();

        
    };
    
    abstract protected String getEntityName();

    //    @SuppressWarnings("unchecked")
    //    private EntityProxyId<P> cast(PlaceParamMap paramMap)
    //    {
    //        return (EntityProxyId<P>) ProxyPlaceUtil.getProxyId(paramMap, m_factory.getApplicationRequestFactory());
    //    }
    private void fireRangeRequest(final Integer firstResult, final Integer maxResult,
        final IServiceRequestCallback<ResultContainer<P>, String> callback)
    {
        FindEntityRangeParams params = FindEntityRangeParams.createEmptyParams();
        params.setAsc(false);
        params.setEntityName(getEntityName());
        params.setFirstResult(firstResult);
        params.setMaxResults(maxResult);
        params.setOrderBy("id");
        params.setFilter(getEntityRangeFilter());
        RequestFactory.findEntityRange(params, callback);
    }

    protected abstract JavaScriptObject getEntityRangeFilter();
    
    private void fireFindRequest(final Integer entityId,
        final IServiceRequestCallback<P, String> callback)
    {
        FindEntityParams params = FindEntityParams.createEmptyParams();
        params.setEntityName(getEntityName());
        params.setId(entityId);
        RequestFactory.findEntity(params, callback);
    }

    private void fireCountRequest(final IServiceRequestCallback<Integer, String> callback)
    {
        RequestFactory.countEntity(getEntityName(), callback);
    }

    private void getLastPage()
    {
        fireCountRequest(new IServiceRequestCallback<Integer, String>()
        {
            public void ok(Integer response)
            {
                if (view == null)
                {
                    // This activity is dead
                    return;
                }
                HasData<P> table = getView().asHasData();
                int rows = response.intValue();
                table.setRowCount(rows, true);
                if (rows > 0)
                {
                    int pageSize = table.getVisibleRange().getLength();
                    int remnant = rows % pageSize;
                    if (remnant == 0)
                    {
                        table.setVisibleRange(rows - pageSize, pageSize);
                    }
                    else
                    {
                        table.setVisibleRange(rows - remnant, pageSize);
                    }
                }
                onRangeChanged(table);
            }

            public void error(String data)
            {
                ExceptionHandler.handle(data);
            }
        });
    }

    private void init()
    {
        fireCountRequest(new IServiceRequestCallback<Integer, String>()
        {
            public void ok(Integer response)
            {
                if (view == null)
                {
                    // This activity is dead
                    return;
                }
                getView().asHasData().setRowCount(response.intValue(), true);
                onRangeChanged(view.asHasData());
            }

            public void error(String data)
            {
                ExceptionHandler.handle(data);
            }
        });
    }

    private void update(Integer entityId)
    {
        final Integer row = idToRow.get(entityId);
        if (row == null)
        {
            return;
        }
        fireFindRequest(entityId, new IServiceRequestCallback<P, String>()
        {
            public void ok(P response)
            {
                getView().asHasData().setRowData(row, Collections.singletonList(response));
            }

            public void error(String data)
            {
                ExceptionHandler.handle(data);
            }
        });
    }
//    private void remove(Integer entityId)
//    {
//        final Integer row = idToRow.get(entityId);
//        if (row == null)
//        {
//            return;
//        }
//        getView().asHasData().setRowData(row, new LinkedList());
//    }
}
